﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DataModels
{
    /// <summary>
    /// A Custom and Generic Data structure that adds convenient functionality
    /// </summary>
    /// <typeparam name="T">The type of the collention</typeparam>
    public class Collection<T>
    {        
        LinkedList<T> collection;

        public Collection(){
            collection = new LinkedList<T>();
        }
        public Collection(LinkedList<T> collection){
            this.collection = collection;
        }
        public Collection(T[] collection){
            this.collection = new LinkedList<T>(collection);
        }
        
        public void addLast(T addMe){
            collection.AddLast(addMe);
        }
        public void addFirst(T addMe){
            collection.AddFirst(addMe);
        }
        public int getSize(){
            return collection.Count;
        }
        /// <summary>
        /// Perform an Action on every object in the collection
        /// </summary>
        /// <param name="applyTheFunc">The Action To perform</param>
        public void applyToAll(Action<T> applyTheFunc){
            LinkedListNode<T> curNode = collection.First;
            while (curNode != null)
            {
                applyTheFunc(curNode.Value);
                curNode = curNode.Next;
            }
        }
        /// <summary>
        /// To weed out elements that dont meet a certain criteria
        /// </summary>
        /// <param name="predicate">
        /// The fuction with a boolean return value to determine if the value
        /// will be in the new Collection
        /// </param>
        /// <returns>
        /// A copy of the linked list that fit a certain criteria
        /// </returns>
        public Collection<T> getCollectionWhere(Func<T, bool> predicate)
        {
            LinkedList<T> rightValues
                = (LinkedList<T>)collection.Where<T>(predicate);

            return new Collection<T>(rightValues);
        }

        ///todo Fix plx 
        public T getElementAtAndDelete(int index)
        {
            T returnMe = getElementAt(index);
            collection.Remove(returnMe);
            return returnMe;
        }

        /// <summary>
        /// Get the element at the specified index 
        /// </summary>
        /// <param name="index">location of the element to receive</param>
        /// <returns>A single element of the collection</returns>
        public T getElementAt(int index)
        {
            int i = 0;
            LinkedListNode<T> curNode = collection.First;
            while (curNode != null && i <= index)
            {
                if (i == index)
                    return curNode.Value;
                i++;
                curNode = curNode.Next;
            }
            throw new Exception("Out Of Bounds");
        }
        /// <summary>
        /// Randomizes the collection
        /// </summary>
        public void shuffle()
        {
            T[] shuffleList = collection.ToArray();
            shuffleArrayInPlace(shuffleList);
            this.collection = new LinkedList<T>(shuffleList);
        }
        /// <summary>
        /// Make a copy of the collection
        /// </summary>
        /// <returns>A Copy of the original collection</returns>
        public Collection<T> getACopy()
        {
            T[] copyList = collection.ToArray();
            LinkedList<T> newCollection = new LinkedList<T>(copyList);
            Collection<T> returnMe = new Collection<T>(newCollection);
            return returnMe;
        }

        /// <summary>
        ///  Removes multiple elements from the end of the collection
        /// </summary>
        /// <param name="amountToKeep">
        /// The number of elements to be kept from the beginning
        /// </param>
        public void keepFirst(int amountToKeep)
        {
            while (collection.Count > amountToKeep)
            {
                removeLast();
            }
        }

        /// <summary>
        ///  Removes multiple elements from the beginning of the collection
        /// </summary>
        /// <param name="amountToKeep">
        /// The number of elements to be kept from the beginning
        /// </param>
        public void keepLast(int amountToKeep)
        {
            while (collection.Count > amountToKeep)
            {
                removeFirst();
            }
        }

        /// <summary>
        /// Remove several elements from the beginning of the Collection
        /// </summary>
        /// <param name="removeAmount">
        /// The number of elements to be removed from the end
        /// </param>
        public void removeLast(int removeAmount)
        {
            for (int i = 0; i < removeAmount; i++)
            {
                removeLast();
            }
        }
        /// <summary>
        /// remove a single element from the end of the collection
        /// </summary>
        public void removeLast()
        {
            collection.RemoveLast();
        }

        /// <summary>
        /// Remove several elements from the beginning of the Collection
        /// </summary>
        /// <param name="removeAmount">
        /// The number of elements to be removed from the end
        /// </param>
        public void removeFirst(int removeAmount)
        {
            for (int i = 0; i < removeAmount; i++)
            {
                removeFirst();
            }
        }
        /// <summary>
        /// remove a single element from the beginning
        /// </summary>
        public void removeFirst()
        {
            collection.RemoveFirst();
        }
        /// <summary>
        /// Get several random elements from the collection
        /// </summary>
        /// <param name="amount">the size of the sample to return</param>
        /// <returns>A collection of size(amount)</returns>
        public Collection<T> getRandomSample(int amount)
        {
            if (amount > getSize())
                throw new Exception("amount is greater than size of List");
            Collection<T> returnMe = getACopy();
            returnMe.shuffle();
            returnMe.keepFirst(amount);
            return returnMe;
        }
        /// <summary>
        /// Converts a collection to a LinkedList
        /// </summary>
        /// <returns>
        /// A Linked list version of the collection
        /// </returns>
        public LinkedList<T> getList()
        {
            return collection;
        }

        private void shuffleArrayInPlace(T[] shuffleList)
        {
            int size = shuffleList.Length;

            for (int i = 0; i < size; i++)
            {
                int rand = staticRandom.getRandomInt(i, size - 1);
                T temp = shuffleList[i];
                shuffleList[i] = shuffleList[rand];
                shuffleList[rand] = temp;
            }
        }

    }
}
